import numpy as np
import random
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import Adam


# 定义迷宫环境
class Maze:
    def __init__(self, size):
        self.size = size
        self.maze = np.zeros((size, size))
        self.reset()

    def reset(self):
        self.maze = np.zeros((self.size, self.size))
        self.start = (0, 0)
        self.end = (self.size - 1, self.size - 1)
        self.maze[self.start] = 1
        self.maze[self.end] = 2
        for i in range(self.size):
            self.maze[i, 0] = -1
            self.maze[i, self.size - 1] = -1
            self.maze[0, i] = -1
            self.maze[self.size - 1, i] = -1
        return self.maze

    def step(self, action):
        x, y = self.start
        if action == 0:  # 上
            x = max(0, x - 1)
        elif action == 1:  # 下
            x = min(self.size - 1, x + 1)
        elif action == 2:  # 左
            y = max(0, y - 1)
        elif action == 3:  # 右
            y = min(self.size - 1, y + 1)
        self.start = (x, y)
        done = (x, y) == self.end
        reward = -1 if not done else 100
        return self.maze, reward, done


# 定义Deep Q-Learning模型
class DQN:
    def __init__(self, state_size, action_size):
        self.state_size = state_size
        self.action_size = action_size
        self.memory = []
        self.gamma = 0.95
        self.epsilon = 1.0
        self.epsilon_min = 0.01
        self.epsilon_decay = 0.995
        self.learning_rate = 0.001
        self.model = self._build_model()

    def _build_model(self):
        model = Sequential()
        model.add(Dense(24, input_dim=self.state_size, activation="relu"))
        model.add(Dense(24, activation="relu"))
        model.add(Dense(self.action_size, activation="linear"))
        model.compile(loss="mse", optimizer=Adam(lr=self.learning_rate))
        return model

    def remember(self, state, action, reward, next_state, done):
        self.memory.append((state, action, reward, next_state, done))

    def act(self, state):
        if np.random.rand() <= self.epsilon:
            return random.randrange(self.action_size)
        act_values = self.model.predict(state)
        return np.argmax(act_values[0])

    def replay(self, batch_size):
        minibatch = random.sample(self.memory, batch_size)
        for state, action, reward, next_state, done in minibatch:
            target = reward
            if not done:
                target = reward + self.gamma * np.amax(
                    self.model.predict(next_state)[0]
                )
            target_f = self.model.predict(state)
            target_f[0][action] = target
            self.model.fit(state, target_f, epochs=1, verbose=0)
        if self.epsilon > self.epsilon_min:
            self.epsilon *= self.epsilon_decay


# 训练和测试
maze = Maze(10)
state_size = maze.size * maze.size
action_size = 4
dqn = DQN(state_size, action_size)
episodes = 1000
batch_size = 32
for e in range(episodes):
    state = maze.reset()
    # state = state.flatten()
    state = state / float(state_size)
    done = False
    while not done:
        action = dqn.act(np.array([state]))
        next_state, reward, done = maze.step(action)
        next_state = next_state.flatten()
        next_state = next_state / float(state_size)
        dqn.remember(state, action, reward, next_state, done)
        state = next_state
        if len(dqn.memory) > batch_size:
            dqn.replay(batch_size)
    if e % 10 == 0:
        print("Episode: {}/{}, Epsilon: {:.2}".format(e, episodes, dqn.epsilon))
